#include <math.h>
#include <time.h>
#include <windows.h>
#include "pThreads/semaphore.h"
#include <vector>
#include "CSemaphore.h"
#include <algorithm>
#include "MySQL/mysql.h"

#include "CNpc.h"
#include "CUtil.h"
#include "CNpcPacket.h"
#include "CSpawnPointHandler.h"
#include "CZoneHandler.h"
#include "CNpcFadeOutPacket.h"
#include "CConsole.h"
#include "CGlobal.h"
#include "CDb.h"
#include "CMobHandler.h"
#include "CSpawnMobPool.h"
#include "CMessagePacket.h"
#include "CMessagePacketTwo.h"
#include "CStats.h"
#include "CActionPacket.h"
#include "Utils/T2List.h"
#include "Utils/T2AvlList.h"
#include <sys/timeb.h>

CNpc::CNpc(void)
{
	//CConsole::outDebOnly("CNpc++ (Addr: %.8X)",this);
	memset(m_name, 0 , 17);
	this->m_isNm = false;
	this->alwaysZoneUpdate = false;
	this->m_isPet = false;
	this->m_isCharSpawned = false;
	this->ownerId = 0;
	this->m_walkCnt = 0;
	this->m_spawnTime = clock();
	this->incombat = false;
	this->m_pIdPoolEntry = NULL;
	this->tod = 0;
	this->m_behaviour = 0;
	this->m_pSP = NULL;
	this->m_isAgressive = false;
	this->m_mobType = 0;
	this->m_isRoaming = false;
	this->m_goalReachedTime = 120;
	this->updatetime = 0;
	this->m_pLastDD = NULL;
	this->m_timeOfKill = 0;

	enmityList = new T3ListProtected<float,CEnmityObject*,T2AVLListInverted>;
	enmityVCList = new T3ListProtected<unsigned int,enmityVC*,T2AVLListInverted>;
}

CNpc::~CNpc(void)
{
	clearEnmityList();
	enmityList->lock();
	delete enmityList;
	delete enmityVCList;
}

int CNpc::face(struct position p) {
	int rot, oldrot;
	oldrot = this->loc.p.rotation;

	double alt = ( p.z - this->loc.p.z ) / ( p.x - this->loc.p.x );

	/*rot = (int) (atan(alt)*45)*-1;
	if(this->loc.p.x > p.x) {
		rot = (int) ((atan(alt)*45)*-1)+0x85;
	}
	this->loc.p.rotation = rot;*/

	rot = (int) ((float)((atan(alt)*45)*-1)/1.41f);
	if(this->loc.p.x > p.x) {
		rot += 255/2;
	}
	this->loc.p.rotation = rot;

	
	if(rot != oldrot) {
		return 1;
	}
	return 0;

}

int CNpc::moveTo(struct position p) {

	//CConsole::outDebOnly("Distance: %.3f", CUtil::distance(this->loc.p, p));

	if(this->m_isRoaming && (!this->incombat)) {
		this->moveState = 1;
		this->m_speed = 0x28;
	} else {
		this->moveState = 2;
		this->m_speed = 0x28;
	}


	float angle = CUtil::calcAngle(this->loc.p.x, this->loc.p.z, p.x, p.z) * float(M_PI) / 180.0f;
	float x = (float)(cosf(angle) * (this->m_speed/0x28) * (this->moveState) * 1.08);
	float z = (float)(sinf(angle) * (this->m_speed/0x28) * (this->moveState) * 1.08);

	position newPos;

	newPos.x = this->loc.p.x + x;
	newPos.y = p.y;
	newPos.z = this->loc.p.z + z;
	newPos.rotation = this->loc.p.rotation;

	if(this->incombat) {
		if((CUtil::distance(this->loc.p, p)) > 3+this->sizeMod)
			this->loc.p = newPos;
	} else {
		this->face(p);
		p.rotation = this->loc.p.rotation;
		if(CUtil::distance(this->loc.p, p) < CUtil::distance(this->loc.p, newPos)) {
			this->loc.p = p;
			if(this->m_isRoaming) {
				this->m_goalReachedTime = time(NULL);
				this->m_isRoaming = false;
			}
		} else {
			this->loc.p = newPos;
		}
	}

	if(((this->loc.p.x != this->old_p.x) || (this->loc.p.y != this->old_p.y) || (this->loc.p.z != this->old_p.z))) {
		this->m_walkCnt += ((0x36*(this->m_speed/0x28)) - (0x14*(this->moveState - 1)));
		this->old_p = this->loc.p;
		if(this->m_walkCnt > 0x2fff) {
			this->m_walkCnt = 0;
		}
		return 1;
	} else {
		return 0;
	}

}


int CNpc::updateNpc(unsigned char * data){
	unsigned long size = 0;
	data[size] = 0x0E;
	if(this->type == 0x57 ){
		memcpy(data+size+0x30, &(this->lookc), 20);
		data[size+1] = 0x28;
	}
	else if(this->type == 0x07 ) {				
		data[size+1] = 0x20;
		memcpy(data+size+0x30, &(this->look), 4);
		memcpy(data+size+0x34, this->m_name, 4);
		memcpy(data+size+0x38, &(this->timestamp), 4);
	}
	else {
		data[size+1] = (unsigned char)((0x33+strlen((const char*)this->m_name)+sizeof(int))/4)*2;
		memcpy(data+size+0x30, &(this->look), 4);
		memcpy(data+size+0x34, this->m_name, strlen((const char*)this->m_name));
	}
	memcpy(data+size+4, &this->m_id, 4);
	unsigned short targId = this->getTargId();
	memcpy(data+size+8, &targId, 2);
	data[size+0x19] = 0x80;
	data[size+0x1f] = this->getAnimation();
	data[size+10] = this->type;

	data[size+0x1c] = 0x28;		
	data[size+0x1d] = 0x28;
	data[size+0x1E] = 0x64;
	data[size+0x27] = 0x20;

	data[size+11] = (unsigned char)this->loc.p.rotation;
	
	memcpy(data+size+12, &this->loc.p.x , 4);
	memcpy(data+size+16, &this->loc.p.y , 4);
	memcpy(data+size+20, &this->loc.p.z , 4);
	memcpy(data+size+24, &this->flag, 4);
	
	memcpy(data+size+0x2c, &this->ownerId, 4);
	memcpy(data+size+0x20, &this->status, 4);
	size += data[1]*2;	

	return size;
}

void CNpc::sendUpdate() {
	CChar::insertPacketGlob(CHAR_INRANGE, CNpcPacket(this), this);
}

void CNpc::sendUpdateZone() {
	CChar::insertPacketGlob(CHAR_INZONE, CNpcPacket(this), this);
}


void CNpc::disengage() {
	this->setAnimation(0x00);
	this->update = false;
	// update the character animation
	this->sendUpdate();
}

void CNpc::clearEnmityList(void)
{
	//remove all chars from the enmityList
	this->enmityList->lock();
	while (this->enmityList->getSize()>0)
	{
		delete (this->enmityList->getFirst());
		this->enmityList->removeAt(0);
	}
	this->enmityList->unlock();
}

void CNpc::clearEnmityVCList(void)
{
	//remove all chars from the enmityVCList
	this->enmityVCList->lock();
	while (this->enmityVCList->getSize()>0)
	{
		this->enmityVCList->removeAt(0);
	}
	this->enmityVCList->unlock();
}


void CNpc::die() 
{
	this->clearEnmityList();
	this->clearEnmityVCList();
	this->h.hp = 0;
	// update the character animation
	this->sendUpdate();
	this->update = false;
}

void CNpc::engage(CBaseEntity *target) {
	this->setAnimation(0x01);
	// update the character animation
	this->sendUpdate();
}

void CNpc::takePhysicalDamage(int damage, CBaseEntity * pDamageDealer) {
	// this would need checking for all sorts of additional effects

	this->incombat = true;
	this->update = true;
	CConsole::outDebOnly("CharBattleServer: Updating enmity for %s", pDamageDealer->getName());
	this->updateEnmity(pDamageDealer,damage * (int)this->getVModifier(), damage * (int)this->getCModifier());
	if(this->ownerId == 0) {
		this->ownerId = pDamageDealer->getId();
	}

	this->m_pLastDD = pDamageDealer;

	this->h.hp -= damage;
	if(this->getHp() <= 0) {
		this->die();
		
	} else {
		this->sendUpdate();
	}

}

/*void CNpc::roam(spawnpoint * spawnPoint) {
	// NPC/MOB is walking
	this->moveState = 1;

	// initial spawn at random node (temporary: no check if multiple mobs occupy initial spawn node)
	//if(this->roamTime == 0) {
	//	this->loc.p.rotation = spawnPoint->nodeList.at(this->atNode).rotation;
	//	this->loc.p.x = spawnPoint->nodeList.at(this->atNode).x;
	//	this->loc.p.y = spawnPoint->nodeList.at(this->atNode).y;
	//	this->loc.p.z = spawnPoint->nodeList.at(this->atNode).z;
	//	this->roamTime = clock();
	//}
					
	if(spawnPoint->nodeList.size() > 1) {
		if((clock() - this->roamTime) >= 15*CLOCKS_PER_SEC){
			this->walkNumNodes = (int)rand()%10 + 6;  // walk 6 - 15 nodes
			unsigned short goToNode = (this->atNode + this->walkNumNodes)%((unsigned short)spawnPoint->nodeList.size());
			if(!spawnPoint->nodeList.at(goToNode).occupied){
				spawnPoint->nodeList.at(this->atNode).occupied = false;
				spawnPoint->nodeList.at(goToNode).occupied = true;
			}
		}
	
		//move entity
		if(this->walkNumNodes > 0){
			position newPosition;
			newPosition.rotation = spawnPoint->nodeList.at(this->atNode).rotation;
			newPosition.x = spawnPoint->nodeList.at(this->atNode).x;
			newPosition.y = spawnPoint->nodeList.at(this->atNode).y;
			newPosition.z = spawnPoint->nodeList.at(this->atNode).z;
			if(((long long)clock() - this->oldtime) >= 350) {
				if( this->moveTo(newPosition) ){
					this->sendUpdate();
					this->roamTime = clock();
				} else {
					this->atNode = (this->atNode+1)%((unsigned short)spawnPoint->nodeList.size());
					this->walkNumNodes--;
				}
				this->oldtime = clock();
			}
		}
	}
}
*/
int seed = 0;
void CNpc::checkAction() {
	if(this->isDead() && this->tod == 0 && (this->m_timeOfKill - time(NULL) < 0) ) {
		this->update = false;
		this->setAnimation(0x03);
		this->tod = (unsigned long)time(NULL);

		if((this->m_pLastDD != NULL) && this->m_pLastDD->isPlayer()) {
			((CChar*)this->m_pLastDD)->insertPacket(CHAR_INRANGE_SELF,CMessagePacket(((CChar*)this->m_pLastDD), this, 0, 0, 0x06));
			int baseExp = this->getRealBaseExp(((CChar*)this->m_pLastDD)->m_lvlMain);
			if(baseExp) {
				// distribute the exp if there is some
				((CChar*)this->m_pLastDD)->distributeExp(baseExp, this);
			}
			this->getDrops(((CChar*)this->m_pLastDD));

		} else if(this->m_pLastDD != NULL) {
			if(this->ownerId != 0) {
				CChar * schar = CChar::getCChar(this->ownerId,"",0);
				if (schar != NULL) {
					schar->insertPacket(CHAR_INRANGE_SELF,CMessagePacket(schar, this, 0, 0, 20));
				}
			}

		}
		this->sendUpdate();
		CreateThread(0,0,Despawn,this,0,0);
	}

	if(!this->isDead()) {

		if(this->incombat) {
			this->inBattle();
			if(this->getObjectType() == OBJ_MOB) {
				CBaseEntity * target;
				target = ((CNpc *)this)->getHighestEnmity();

				if( (target == NULL) || (target->getZone() != this->getZone()) ) {
					this->incombat = false;
					this->update = false;
					this->setAnimation(0);
					if(target != NULL) {
						this->removeEnmity(target);
					}
					if(this->ownerId != 0)
						this->ownerId = 0;
					//CreateThread(0,0,Despawn,this,0,0);
				} else {
					if(this->getAnimation() != 1) {
						this->setAnimation(1);
					}
				}
			}
		}

		// IDLE
		else {
			if(seed > 30000) {
				seed = 0;
			}
			srand(time(NULL)+seed++);
			if((this->m_familyId != 192) &&
			   !this->m_isRoaming && 
			   (this->m_pSP != NULL) &&
			   (time(NULL) - this->m_goalReachedTime) > 12+rand()%30) { 

				struct position * newRoamPoint;
				newRoamPoint = new position();
pos:
				srand(time(NULL)+seed++);
				newRoamPoint->x = (this->m_pSP->m_pos.x -10 ) + rand()%20;
				srand(time(NULL)+seed++);
				newRoamPoint->z = (this->m_pSP->m_pos.z -10 ) + rand()%20;
				newRoamPoint->y = this->m_pSP->m_pos.y;
				if(CUtil::distance(*newRoamPoint,this->loc.p) < 3) {
					goto pos;
				}
				this->targ_p = *newRoamPoint;
				this->m_isRoaming = true;

			} else if(this->m_isRoaming && ((clock() - this->updatetime) > 600)) {

				this->updatetime = clock();
				this->moveTo(this->targ_p);
				this->sendUpdate();

			}

		/*	CSpawnPointHandler::lockSpawnPointList(this->getZone());
			if ((CSpawnPointHandler::spawnPointList[this->getZone()].size() > 0) && (CZoneHandler::getInstance()->npcZoneList[this->getZone()].size() > 0)) {
				spawnpoint * spawnPoint;
				spawnPoint =  CSpawnPointHandler::getSpawnPoint(this->spawnId, (unsigned char)this->getZone());
				this->roam(spawnPoint);
			}
			CSpawnPointHandler::unlockSpawnPointList(this->getZone());*/
		}
	}
}

void CNpc::inBattle() {
	CBaseEntity * target = NULL;
	// NPC/MOB is running
	this->moveState = 2;

	if(((long long)clock() - this->oldtime) > 400){
		if(this->getObjectType() == OBJ_MOB) {
			CNpc * ptrMob = (CNpc*)this;
			target = ptrMob->getHighestEnmity();
		} else {
			target = (CChar *)this->m_pTarget;
		}
		// Npc has no target, therefor should be friendly again.
		if(target == NULL) {
			this->disengage();
		} else {
			bool update = false;
			// if any position change happens, update the mob to the players						
			if(this->face( target->loc.p )) {
				update = true;
			
			}
			if(this->moveTo(target->loc.p)) {
				update = true;
			}
			if(update) {
				this->sendUpdate();
			}

		}
		this->oldtime = clock();
	}

	if (target != NULL) {
		//Can the mob attack yet (3 second attack delay)
		if((((long long)clock() - this->oldAttackTime) > ((this->m_defaultDelay/60)*1000))&&(CUtil::distance(this->loc.p, target->loc.p) <= (3+this->sizeMod))){
			// COMBAT STUFF REMOVED

			if(target->isDead()) 
			{
				CNpc::removeEnmity(target);
				if(this->enmityList->getSize() == 0) 
				{
					this->ownerId = 0;
					this->setAnimation(0);
					this->sendUpdate();
				}
				this->m_pTarget = NULL;

			}


	
			if(rand()%100 < this->getHitRate(target)) { 
				this->attackAction();
			} else {
				this->missedAttackAction(target);
			}
			

			// Time the next attack
			this->oldAttackTime = (long long) clock();
		}
	}
}

void CNpc::attackAction() {

	int damage = 0;
	CBaseEntity * target = this->getHighestEnmity();

	if(( target == NULL )|| target->isDead()) {
		return;
	}

	float ratio = CUtil::randFloat(this->getMinCRatio(target), this->getMaxCRatio(target));

	bool critical = false;
	if(target == NULL) {
		return;
	}
	// seed the random
	srand(clock());

	int crithitrate = 5;
	int playerdex = this->stats.dex + this->stats.pDex;
	int monsteragi = target->stats.agi + target->stats.pAgi;
	int dDEX =  playerdex - monsteragi;
	
	if (dDEX < 0){
		dDEX = 0;
	}
	if (dDEX > 50){
		dDEX = 50;
	}
	//dDEX is between 0 and 50.
	int crithitbonus = (int)(dDEX * 0.3);

	//add on the bonus to the base....
	crithitrate += crithitbonus;

	unsigned short dmg = this->getWeaponDamage();


	if((rand()%100) <= crithitrate) {
		critical = true;
		ratio += 1;

		if (ratio > 3){
			ratio = 3;
		}
	}

	damage = (int)((dmg + this->getFStr(target))*ratio);

	CActionPacket* attackPacket=new CActionPacket();

	attackPacket->setActorId(this->getId());
	attackPacket->setCategorie(1);
	attackPacket->setStartAnimationCategorie(0x1D);
	attackPacket->setStartAnimation(0x306B);
	attackPacket->setParameter1(0x0061);
	attackPacket->setUnknown(0x00000000);

	apActionEntry newEntry;
	memset(&newEntry,0,sizeof(newEntry));

	newEntry.unknownValue=0x08;
	newEntry.targetAnimationID=0x0100;
	newEntry.unknownValue2=0x020;

	newEntry.messageType=0x0001;
	
	newEntry.additionalMessages=0x00000000;
	newEntry.dynamisTimerFlag=0x0;
	
	float tpperhit = 0;

	if(damage > 0) {

		int delay = 240;
	
		if (delay <= 180){
			tpperhit = (short)((((delay-180)*1.5)/180)+5);
		}
		else if (delay > 180 && delay <= 450){
			tpperhit = (short)((((delay-180)*6.5)/270)+5);
		}
		else if (delay > 450 && delay <= 480){
			tpperhit = (short)((((delay-450)*1.5)/30)+11.5);
		}
		else if (delay > 480 && delay <= 530){
			tpperhit = (short)((((delay-480)*1.5)/50)+13);
		}
		else if (delay > 530 && delay <= 999){
			tpperhit = (short)((((delay-530)*3.5)/470)+14.5);
		}
		else{
			printf ("Error with delay. Value: %i", tpperhit);
		}
	}

	if(this->h.tp + tpperhit < 300) {	
		this->h.tp += (short)tpperhit;
	} else {
		this->h.tp = 300;
	}

	if(target->h.tp + tpperhit < 300) {	
		target->h.tp += (short)(tpperhit/3);
	} else {
		target->h.tp = 300;
	}

	//this->updateHealth();

	if(critical) {
		newEntry.unknownValue2=0x044;
		newEntry.messageType=0x0043;
	}

	newEntry.parameter2=damage;

	attackPacket->setTargetID(1,target->m_id);
	attackPacket->setActionEntry(1,1,&newEntry);
	


	attackPacket->finalizePacket();
	//attackPacket->savePacket();
	if (target->isPlayer()) {
		((CChar *)target)->insertPacket(CHAR_INRANGE_SELF,*attackPacket);
	} else {
		CChar::insertPacketGlob(CHAR_INRANGE,*attackPacket,target);
	}
	target->takePhysicalDamage(damage, this);



}

DWORD WINAPI Despawn(LPVOID lpParam) {
	CNpc * ptrNpc = ((CNpc*)lpParam);

	Sleep(15000);

	CChar::insertPacketGlob(CHAR_INZONE, CNpcFadeOutPacket(ptrNpc), ptrNpc);

	Sleep(2000);

	ptrNpc->status = 0x07;
	ptrNpc->sendUpdate();

	srand(clock());
	Sleep(1000+rand()%3000);

	CConsole::outDebOnly("Unsetting mob %x: backtrack: %x",ptrNpc,ptrNpc->m_pIdPoolEntry->pMob);
	ptrNpc->m_pIdPoolEntry->tod = time(NULL);
	ptrNpc->m_pIdPoolEntry->pMob = NULL;
	ptrNpc->m_pIdPoolEntry->pInfo->isAlive = false;
	if(ptrNpc->m_pSP != NULL) {
		ptrNpc->m_pSP->m_pMob = NULL;
		ptrNpc->m_pSP->tod = time(NULL);
		ptrNpc->m_pSP->m_pParentPool->m_currentSpawns--;
	}
	CZoneHandler::getInstance()->removeMob(ptrNpc->getId());
	delete ptrNpc;
	return true;
}

DWORD WINAPI DespawnNoWait(LPVOID lpParam) {
	CNpc * ptrNpc = ((CNpc*)lpParam);

	CChar::insertPacketGlob(CHAR_INZONE, CNpcFadeOutPacket(ptrNpc), ptrNpc);

	Sleep(2000);

	ptrNpc->status = 0x07;
	ptrNpc->sendUpdate();

	srand(clock());
	Sleep(1000+rand()%3000);

	CConsole::outDebOnly("Unsetting mob %x: backtrack: %x",ptrNpc,ptrNpc->m_pIdPoolEntry->pMob);
	ptrNpc->m_pIdPoolEntry->tod = time(NULL);
	ptrNpc->m_pIdPoolEntry->pMob = NULL;
	ptrNpc->m_pIdPoolEntry->pInfo->isAlive = false;
	if(ptrNpc->m_pSP != NULL) {
		ptrNpc->m_pSP->m_pMob = NULL;
		ptrNpc->m_pSP->tod = time(NULL);
		ptrNpc->m_pSP->m_pParentPool->m_currentSpawns--;
	}
	CZoneHandler::getInstance()->removeMob(ptrNpc->getId());
	delete ptrNpc;
	return true;
}

unsigned short CNpc::getTargId() {
	return this->m_targId;
}

void CNpc::openDoor() {
	this->setAnimation(0x09);
	this->sendUpdate();
	this->setAnimation(0x08);
	this->sendUpdate();
}

void CNpc::closeDoor() {
	this->setAnimation(0x08);
	this->sendUpdate();
	this->setAnimation(0x09);
	this->sendUpdate();
}

int CNpc::getCappedBaseExp(int charLevel) {
	int count;
	char query[8096];
	unsigned short fl = 0x01;
	MYSQL_RES * res;
	MYSQL_ROW row;
	int levelDif =  this->m_lvlMain - charLevel ;

	if(levelDif >= 15) {
		if(charLevel <= 50) {
			return (int)(200*CGlobal::expRate);
		} else if(charLevel <= 60) {
			return (int)(250*CGlobal::expRate);
		} else if(charLevel >= 61) {
			return (int)(300*CGlobal::expRate);
		}

	} else if(levelDif <= -20) {
		return 0;
	}

	memset(query,0,8096);
	sprintf(query, "SELECT r%i FROM exp_base WHERE levelDif = %i", (int)ceil(((float)charLevel / 5)), levelDif);
	CConsole::outTime("%s",query);
	count = (int) strlen(query);

	MYSQL* mysqlConn=CDb::getConn(); //get conn
	mysql_real_query(mysqlConn, query, count);

	res = mysql_store_result(mysqlConn);
	CDb::pushConn(mysqlConn); //release conn

	if(!res) {
		return 0;
	}

	if(row = mysql_fetch_row(res)) {
		int baseExp = atoi(row[0]);
		mysql_free_result(res);
		if((charLevel <= 50) && (baseExp > 200)) {
			return (int)(200*CGlobal::expRate);
		} else if((charLevel <= 60) && (baseExp > 250)){
			return (int)(250*CGlobal::expRate);
		} else if((charLevel >= 61)  && (baseExp > 300)){
			return (int)(300*CGlobal::expRate);
		} else {
			return (int)(baseExp*CGlobal::expRate);
		}
	} else {
		mysql_free_result(res);
		return 0;
	}	
}

void CNpc::getDrops(CChar * schar) {
	int count;
	char query[8096];
	unsigned short fl = 0x01;
	MYSQL_RES * res;
	MYSQL_ROW row;
	
	memset(query,0,8096);
	sprintf(query, "SELECT type, itemId, rate FROM droplist WHERE dropId = %i AND type = 0", this->m_dropList);
	CConsole::outDebOnly("%s",query);
	count = (int) strlen(query);

	MYSQL* mysqlConn=CDb::getConn(); //get conn
	mysql_real_query(mysqlConn, query, count);

	res = mysql_store_result(mysqlConn);
	CDb::pushConn(mysqlConn); //release conn

	if(!res)
		return;

	while(row = mysql_fetch_row(res)) {
		unsigned short itemID = atoi(row[1]);
		unsigned int rate = atoi(row[2]);
		unsigned int randomChance = (unsigned int)rand()%100;
		if((unsigned int)(rate * CGlobal::dropRate) > randomChance) {
			//Put the drop in the treasure pool
			schar->getTreasurePool()->addItem(itemID,1,this);
		}

	}	
	mysql_free_result(res);

}

void CNpc::setTargId(unsigned int targid) {
	this->m_targId = targid;
}

int CNpc::getRealBaseExp(int charLevel) {
	int count;
	char query[8096];
	unsigned short fl = 0x01;
	MYSQL_RES * res;
	MYSQL_ROW row;
	int exp;
	int levelDif =  this->m_lvlMain - charLevel ;

	if(levelDif >= 15) {
		return 800;
	} else if(levelDif <= -20) {
		return 0;
	}

	memset(query,0,8096);
	sprintf(query, "SELECT r%i FROM exp_base WHERE levelDif = %i", (int)ceil(((float)charLevel / 5)), levelDif);
	CConsole::outDebOnly("%s",query);
	count = (int) strlen(query);

	MYSQL* mysqlConn=CDb::getConn(); //get conn
	mysql_real_query(mysqlConn, query, count);

	res = mysql_store_result(mysqlConn);
	CDb::pushConn(mysqlConn); //release conn

	if(!res) {
		return 0;
	}

	if(row = mysql_fetch_row(res)) {
		exp = atoi(row[0]);
	} else {
		mysql_free_result(res);
		return 0;
	}

	mysql_free_result(res);
	return exp;
}

//Decays VEnmity for everyone in the list
void CNpc::decayVEnmity(float decayAmount)
{
	struct enmityVC * tmpEnmityVC = NULL;
	this->enmityVCList->lock();
	for(unsigned int i = 0; i < this->enmityVCList->getSize(); i++)  {
		tmpEnmityVC = this->enmityVCList->at(i);
		if (tmpEnmityVC==NULL) {
			continue;
		}
		if (tmpEnmityVC->m_vEnmity > 0) {
			if (tmpEnmityVC->m_vEnmity > decayAmount) {
				tmpEnmityVC->m_vEnmity -= decayAmount;
			} else {
				tmpEnmityVC->m_vEnmity = 0;
			}
			this->setEnmity(tmpEnmityVC->m_pEntity,	(tmpEnmityVC->m_vEnmity + tmpEnmityVC->m_cEnmity),false);
		}
		tmpEnmityVC = NULL;
	}
	this->enmityVCList->unlock();
}

//Updates Volatile and Cumulative enmity
void CNpc::updateVCEnmity(CBaseEntity * entity, int vEnmity, int cEnmity) {
	struct enmityVC * tmpEnmityVC = NULL;

	float totalEnmity = 0;

	unsigned int key = entity->getId();
	this->enmityVCList->lock();
	tmpEnmityVC = this->enmityVCList->find(key);
	
	if(tmpEnmityVC != NULL) {
		tmpEnmityVC->m_vEnmity += vEnmity; 
		tmpEnmityVC->m_cEnmity += cEnmity;
		if (tmpEnmityVC->m_vEnmity > 10000.0) { //MAX_VENMITY
			tmpEnmityVC->m_vEnmity = 10000.0; //MAX_VENMITY
		} else if (tmpEnmityVC->m_vEnmity < 0) { //MAX_VENMITY
			tmpEnmityVC->m_vEnmity = 0.0; //MAX_VENMITY
		}

		if (tmpEnmityVC->m_cEnmity > 10000.0) { //MAX_CENMITY
			tmpEnmityVC->m_cEnmity = 10000.0; //MAX_CENMITY
		} else if (tmpEnmityVC->m_cEnmity < 0) { // MAX_CENMITY
			tmpEnmityVC->m_cEnmity = 0.0;
		}
		totalEnmity = tmpEnmityVC->m_cEnmity + tmpEnmityVC->m_vEnmity;
	} else {
		if (this->enmityVCList->getSize() == 0) {
			if ((vEnmity != 0) && (cEnmity != 0)) {
				//This was the first action on the mob that pulled it
				vEnmity += 900;
				cEnmity += 200;
				//Enable mob aggro
				this->setAnimation(0x01);
				this->update = true;
				this->incombat = true;

			}
		}
		struct enmityVC * newEnmityVC = new struct enmityVC();
		newEnmityVC->m_pEntity = entity;
		newEnmityVC->m_vEnmity += vEnmity; 
		newEnmityVC->m_cEnmity += cEnmity;
		totalEnmity = newEnmityVC->m_cEnmity + newEnmityVC->m_vEnmity;
		this->enmityVCList->pushBack(key,newEnmityVC);
	}
	this->enmityVCList->unlock();
	//Update the enmity list
	this->setEnmity(entity,totalEnmity,true);
}


void CNpc::setEnmity(CBaseEntity * entity, float enmity, bool addNew) {
	CEnmityObject * tmpEnmObj = NULL;
	CBaseEntity* tempChar=NULL;
	float keyEmnity = 0;
	this->enmityList->lock();
	for(unsigned int i = 0; i < this->enmityList->getSize() ; i++) {
		tmpEnmObj = this->enmityList->at(i);
		if (tmpEnmObj==NULL) {
			continue;
		}
		tempChar=tmpEnmObj->m_pEntity;
		if(tempChar == entity) {
			keyEmnity = this->enmityList->getKeyAt(i);
			tmpEnmObj = this->enmityList->at(i);
			break;
		}
		tmpEnmObj = NULL;
	}
	if(tmpEnmObj != NULL) {
		float oldKeyEnmity = keyEmnity;

		//CConsole::outDebOnly("New Enmity: %d -> %d", tmpEnmObj->m_enmity,(int)enmity);
		tmpEnmObj->m_enmity = (unsigned int)enmity; 

		keyEmnity = enmity;
		

		while(this->enmityList->changeKey(oldKeyEnmity, keyEmnity) != 0) {
			keyEmnity+=0.01f; //Increase key emnity by 0,1 till we find a free key
		}
	} else if (addNew == true) {
		float keyEmnity = enmity;
		CEnmityObject* newEmnityObject = new CEnmityObject(entity,(int)enmity);
		while(this->enmityList->pushBack(keyEmnity,newEmnityObject) != 0) {
			keyEmnity+=0.01f; //Increase key emnity by 0,1 till we find a free key
		}
	}
	this->enmityList->unlock();
}


void CNpc::removeEnmity(CBaseEntity * entity) {
	CEnmityObject* tempEmnityObject;
	if (this->enmityList != NULL) {
		this->enmityList->lock();
		for(unsigned int i = 0; i < this->enmityList->getSize(); i++) {
			tempEmnityObject = this->enmityList->at(i);
			if( tempEmnityObject->m_pEntity == entity ) {
				delete tempEmnityObject;
				this->enmityList->removeAt(i);
				break;
			}
		}
		this->enmityList->unlock();
	}
}

void CNpc::setName(char * name) {
	memcpy(this->m_name,name,16);
	m_name[16]='\0';
}

CBaseEntity* CNpc::getHighestEnmity() {
	this->enmityList->lock();
	CEnmityObject* tempEmnityObject = this->enmityList->getFirst();
	if (tempEmnityObject == NULL) {
		this->enmityList->unlock();
		return NULL;
	}
	CBaseEntity* retVal = (CChar *)tempEmnityObject->m_pEntity;
	this->enmityList->unlock();
	return retVal;
}

bool CNpc::isPet() {
	return this->m_isPet;
}

void CNpc::isPet(bool pet) {
	this->m_isPet = pet;
}

bool CNpc::isCharSpawned() {
	return this->m_isCharSpawned;
}

void CNpc::isCharSpawned(bool charSpawned) {
	this->m_isCharSpawned = charSpawned;
}

void CNpc::setLookVal(int look) {
	this->look = look;
}


void CNpc::calcStats() {
	
	unsigned short fStr,fDex,fAgi,fInt,fMnd,fChr,fVit;
	unsigned short jStr,jDex,jAgi,jInt,jMnd,jChr,jVit;
	unsigned short mStr,mDex,mAgi,mInt,mMnd,mChr,mVit;

	fStr = this->getBaseToRank(this->m_statRank.str,	this->m_lvlMain);
	fDex = this->getBaseToRank(this->m_statRank.dex,	this->m_lvlMain);
	fAgi = this->getBaseToRank(this->m_statRank.agi,	this->m_lvlMain);
	fInt = this->getBaseToRank(this->m_statRank.inte,	this->m_lvlMain);
	fMnd = this->getBaseToRank(this->m_statRank.mnd,	this->m_lvlMain);
	fChr = this->getBaseToRank(this->m_statRank.chr,	this->m_lvlMain);
	fVit = this->getBaseToRank(this->m_statRank.vit,	this->m_lvlMain);

	jStr = this->getBaseToRank(CStats::getJobGrade(this->m_jobSub,2),	this->m_lvlSub);
	jDex = this->getBaseToRank(CStats::getJobGrade(this->m_jobSub,3),	this->m_lvlSub);
	jVit = this->getBaseToRank(CStats::getJobGrade(this->m_jobSub,4),	this->m_lvlSub);
	jAgi = this->getBaseToRank(CStats::getJobGrade(this->m_jobSub,5),	this->m_lvlSub);
	jInt = this->getBaseToRank(CStats::getJobGrade(this->m_jobSub,6),	this->m_lvlSub);
	jMnd = this->getBaseToRank(CStats::getJobGrade(this->m_jobSub,7),	this->m_lvlSub);
	jChr = this->getBaseToRank(CStats::getJobGrade(this->m_jobSub,8),	this->m_lvlSub);

	mStr = this->getBaseToRank(CStats::getJobGrade(this->m_jobMain,2),	this->m_lvlMain);
	mDex = this->getBaseToRank(CStats::getJobGrade(this->m_jobMain,3),	this->m_lvlMain);
	mVit = this->getBaseToRank(CStats::getJobGrade(this->m_jobMain,4),	this->m_lvlMain);
	mAgi = this->getBaseToRank(CStats::getJobGrade(this->m_jobMain,5),	this->m_lvlMain);
	mInt = this->getBaseToRank(CStats::getJobGrade(this->m_jobMain,6),	this->m_lvlMain);
	mMnd = this->getBaseToRank(CStats::getJobGrade(this->m_jobMain,7),	this->m_lvlMain);
	mChr = this->getBaseToRank(CStats::getJobGrade(this->m_jobMain,8),	this->m_lvlMain);

	if(this->m_lvlSub > 15) {

		jStr /= 2;
		jDex /= 2;
		jAgi /= 2;
		jInt /= 2;
		jMnd /= 2;
		jChr /= 2;
		jVit /= 2;

	} else {
		jStr = 0;
		jDex = 0;
		jAgi = 0;
		jInt = 0;
		jMnd = 0;
		jChr = 0;
		jVit = 0;

	}

	this->stats.str = jStr + fStr + mStr;
	this->stats.dex = jDex + fDex + mDex;
	this->stats.vit = jVit + fVit + mVit;
	this->stats.agi = jAgi + fAgi + mAgi;
	this->stats.inte = jInt + fInt + mInt;
	this->stats.mnd = jMnd + fMnd + mMnd;
	this->stats.chr = jChr + fChr + mChr;

	this->getResist();

}

unsigned short CNpc::getBaseToRank(short rank, short lvl) {


	switch(rank) {
		
		case 1:
			return (unsigned short)(5+(lvl-1)*0.50);
		case 2:
			return (unsigned short)(4+(lvl-1)*0.45);
		case 3:
			return (unsigned short)(4+(lvl-1)*0.40);
		case 4:
			return (unsigned short)(3+(lvl-1)*0.35);
		case 5: 
			return (unsigned short)(3+(lvl-1)*0.30);
		case 6:
			return (unsigned short)(2+(lvl-1)*0.25);
		case 7:
			return (unsigned short)(2+(lvl-1)*0.20);

	}
	
	return 0;

}

unsigned short CNpc::getBaseEva() {

	//Evasion of Monster determined by Job Evasion Ranking + AGI/2 Bonus + Traits
	//getting the variables for calculating EVA

	int mobagi = this->stats.agi;
	int moblvl = this->m_lvlMain;
	// this should be an integer, e.g. WHM is 3, WAR is 1
	int mobjob = this->m_jobMain; 

	//Determining Evasion Rank dependant on Job
	//Rank A = NIN THF
	//Rank B = DNC MNK PUP SAM
	//Rank C = BST BLU DRK DRG PLD WAR
	//Rank D = BRD COR RDM
	//Rank E = BLM RNG SCH SMN WHM
	int mobevaranking = 0;

	//Two formulas, one for Lv1-50, one for 51+
	if (moblvl > 50) {

		//Rank A
		if (mobjob==13 || mobjob==6) {
			mobevaranking = (int)(153+((moblvl-50)*5));
		}

		//Rank B
		if (mobjob==19 || mobjob==2 || mobjob==18 || mobjob==12) {
			mobevaranking = (int)(147+((moblvl-50)*4.9f));
		}

		//Rank C
		if (mobjob==9 || mobjob==16 || mobjob==8 || mobjob==14 || mobjob==7 || mobjob==1) {
			mobevaranking = (int)(142+((moblvl-50)*4.8f));
		}

		//Rank D
		if (mobjob==10 || mobjob==17 || mobjob==5) {
			mobevaranking = (int)(136+((moblvl-50)*4.7f));
		}

		//Rank E
		if (mobjob==4 || mobjob==11 || mobjob==20 || mobjob==15 || mobjob==3) {
			mobevaranking = (int)(126+((moblvl-50)*4.5f));
		}

	}
	else if (moblvl <= 50) {

		//Rank A
		if (mobjob==13 || mobjob==6) {
			mobevaranking = (int)(6+((moblvl-1)*3));
		}

		//Rank B
		if (mobjob==19 || mobjob==2 || mobjob==18 || mobjob==12) {
			mobevaranking = (int)(5+((moblvl-1)*2.9f));
		}

		//Rank C
		if (mobjob==9 || mobjob==16 || mobjob==8 || mobjob==14 || mobjob==7 || mobjob==1) {
			mobevaranking = (int)(5+((moblvl-1)*2.8f));
		}

		//Rank D
		if (mobjob==10 || mobjob==17 || mobjob==5) {
			mobevaranking = (int)(4+((moblvl-1)*2.7f));
		}

		//Rank E
		if (mobjob==4 || mobjob==11 || mobjob==20 || mobjob==15 || mobjob==3) {
			mobevaranking = (int)(4+((moblvl-1)*2.5f));
		}


	}

	//Adding on AGI in evasion...
	int agievasion = mobagi/2;

	//Combining the two and returning the full evasion.
	int evasion = agievasion+mobevaranking;

	// added 0.7 for the time being. it feels way too high otherwise
	return (unsigned short)(evasion * 0.7f);



}

unsigned short CNpc::getBaseDef() {



	//Defense of Monster determined by [Family Defense Ranking + VIT/2 Bonus + Traits] * multiplier

	//getting the variables for calculating DEF
	int mobvit = this->stats.vit;
	int moblvl = this->m_lvlMain;
	//this should be an integer, e.g. Crab is 252, etc
	int mobfam = this->m_familyId; 
	//Mob Family ID obtained via family_system table under FamilyID
	//Note: This is only adjusted for bugards (ID=50), buffalo (ID=49) and antlions (ID=22)
	float multiplier = 1; 

	if (mobfam==50 || mobfam==49 || mobfam==22) {
		multiplier = 1.2f;
	}

	//Determining Defense Rank dependant on Family
	//Rank A = None.
	//Rank B = GiantBird (ID=99)
	//Rank C = Everyone Else.
	//Rank D = Colibri (ID=60)
	//Rank E = Crawlers (ID=64)
	int mobdefranking = 0;

	//Two formulas, one for Lv1-50, one for 51+
	if (moblvl > 50) {

		//Rank B
		if (mobfam==99) {
			mobdefranking =(int)( 147+((moblvl-50)*4.9f));
		}
		//Rank D
		else if (mobfam==60) {
			mobdefranking = (int)(136+((moblvl-50)*4.7f));
		}
		//Rank E
		else if (mobfam==64) {
			mobdefranking = (int)(126+((moblvl-50)*4.5f));
		}
		//Rank C
		else {
			mobdefranking = (int)(142+((moblvl-50)*4.8f));
		}

	}

	else if (moblvl <= 50) {

		//Rank B
		if (mobfam==99) {
			mobdefranking = (int)(5+((moblvl-1)*2.9f));
		}
		//Rank D
		else if (mobfam==60) {
			mobdefranking = (int)(4+((moblvl-1)*2.7f));
		}
		//Rank E
		else if (mobfam==64) {
			mobdefranking = (int)(4+((moblvl-1)*2.5f));
		}
		//Rank C
		else {
			mobdefranking = (int)(5+((moblvl-1)*2.8f));
		}

	}

	//Adding on VIT to defense...
	int vitdef = mobvit/2;

	//Combining the two and returning the full defense, including the multiplier
	int def = (int)((vitdef+mobdefranking)*multiplier);

	return def;


}

int CNpc::getFStr(CBaseEntity * target) {
	int dif = (this->stats.str + this->stats.pStr) - (target->stats.vit + target->stats.pVit);
	int rank = this->m_lvlMain /9; 
	int fstr = 0; 

	if (dif >= 12) {
		fstr = (dif+4)/4;
	} else if(dif >= 1 && dif <= 5) {
		fstr = (dif+6)/4;
	} else if(dif >= 6 && dif <= 11) {
		fstr = (dif+7)/4;
	} else if(dif >= -2 && dif <= 0) {
		fstr = (dif+8)/4;
	} else if(dif >= -7 && dif <= -3) {
		fstr = (dif+9)/4;
	} else if(dif >= -15 && dif <= -8) {
		fstr = (dif+10)/4;
	} else if(dif >= -21 && dif <= -16) {
		fstr = (dif+12)/4;
	} else if(dif <= -22) {
		fstr = (dif+13)/4;
	}

	if(fstr < (rank * (-1))) {
		return (rank * (-1));
	}
	if((fstr > (rank * (-1))) && (fstr <= rank +8)) {
		return fstr;
	} else 
		return rank +8;

}

unsigned short CNpc::getBaseAttack() {
	int skill = 0;
	if(this->m_lvlMain <= 30) {
		skill = (int)((float)this->m_lvlMain * 3.1); 
	} else if(this->m_lvlMain <= 50) {
		skill = (int)((float)this->m_lvlMain * 3.0); 
	} else if(this->m_lvlMain > 50) {
		skill = (int)((float)this->m_lvlMain * 3.7); 
	}
	return (8 + skill + (this->stats.str + this->stats.pStr) /2);
}

unsigned short CNpc::getBaseAccuracy(CBaseEntity * target) {
	int skill = 0;
	if(this->m_lvlMain <= 30) {
		skill = (int)((float)this->m_lvlMain * 3.1); 
	} else if(this->m_lvlMain <= 50) {
		skill = (int)((float)this->m_lvlMain * 3.0); 
	} else if(this->m_lvlMain > 50) {
		skill = (int)((float)this->m_lvlMain * 3.7); 
	}

	int levelbonus = 0;
	int baseAcc = (8 + skill +  (this->stats.dex + this->stats.pDex) /2 );
	if (this->m_lvlMain > target->m_lvlMain) {
		levelbonus = (this->m_lvlMain - target->m_lvlMain) * 4;
		baseAcc += levelbonus;
	}
	else if (target->m_lvlMain > this->m_lvlMain) {
		levelbonus = (target->m_lvlMain - this->m_lvlMain) * 4;
		baseAcc -= levelbonus;
	}

	return baseAcc;
}

unsigned short CNpc::getWeaponDamage() {
	
	if(this->m_lvlMain < 10) {
		return (unsigned short)((float)this->m_lvlMain * 1.4);
	} else if(this->m_lvlMain < 20) {
		return (unsigned short)((float)this->m_lvlMain * 1.3);
	} else if(this->m_lvlMain < 30) {
		return (unsigned short)((float)this->m_lvlMain * 1.2);
	} else if(this->m_lvlMain < 40) {
		return (unsigned short)((float)this->m_lvlMain * 1.1);
	} else {
		return this->m_lvlMain;
	}

}

// makes the mob chase a char
void CNpc::aggroChar(CChar * pChar) {

	this->setAnimation(0x01);
	this->update = true;
	this->incombat = true;
	this->updateEnmity(pChar,0,0);

}

bool CNpc::hasBehaviour(unsigned int behaviour) {

	return (this->m_behaviour & behaviour);

}

void CNpc::getResist() {
	//Actually sets resists via db
	MYSQL_RES * res;
	MYSQL_ROW row;
	char query[8192];
	unsigned long count;

	memset(query, 0, 8192);
	sprintf(query, "SELECT Slash, Pierce, H2H, Impact, `Range`, Fire, Ice, Wind, Earth, Lightning, Water, Light, Dark FROM family_system WHERE FamilyID = %i;",this->m_familyId);
	count = (int) strlen(query);

	MYSQL * mysqlConn=CDb::getConn(); //get conn
	mysql_real_query(mysqlConn, query, count);

	res = mysql_store_result(mysqlConn);
	CDb::pushConn(mysqlConn); //release conn

	if(!res) {
		CConsole::outDebOnly("Error looking up standard resists.");
	} else if(mysql_num_rows(res)){
		row = mysql_fetch_row(res);
		this->r.slash = atof(row[0]);
		this->r.pierce = atof(row[1]);
		this->r.handtohand = atof(row[2]);
		this->r.impact = atof(row[3]);
		this->r.range = atof(row[4]);
		this->r.fire = atof(row[5]);
		this->r.ice = atof(row[6]);
		this->r.wind = atof(row[7]);
		this->r.earth = atof(row[8]);
		this->r.thunder = atof(row[9]);
		this->r.water = atof(row[10]);
		this->r.light = atof(row[11]);
		this->r.dark = atof(row[12]);
	}
	mysql_free_result(res);
}